package com.pocket.insight.base.config.mybatis.typeHandler;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.boot.jackson.JsonComponent;

import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
    static final  ObjectMapper mapper = new ObjectMapper();
    private final Class<T>     clazz;

    static {
        SimpleModule module = new SimpleModule();
        module.addDeserializer(JSONObject.class, new JacksonDeserializer4JSONObject());
        mapper.registerModule(module);
        mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

    public JsonTypeHandler(Class<T> clazz) {
        if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.clazz = clazz;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, JSONUtil.toJsonStr(parameter));
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return toObject(rs.getString(columnName));
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return toObject(rs.getString(columnIndex));
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return toObject(cs.getString(columnIndex));
    }

    /**
     * 序列化为表字段
     * 如果是postgresql数据库，需要转换为 jsonb 的 PGobject 独享
     *
     * @param object
     * @return
     */
    private String toJson(T object) {
        try {
            return toJsonString(object);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 反序列化为对象字段
     *
     * @param content
     * @return
     */
    public T toObject(String content) {
        if (content != null && !content.isBlank()) {
            try {
                // TODO : https://github.com/baomidou/mybatis-plus/issues/4643
                content = content.replaceAll("\\\\\"", "\"");
                if (content.startsWith("\"") && content.endsWith("\"")) {
                    content = content.substring(1, content.length() - 1);
                }
                return mapper.readValue(content, this.clazz);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }

    public String toJsonString(T object) throws Exception {
        return mapper.writeValueAsString(JSON.toJSON(object));
    }

    @JsonComponent
    public static class JacksonDeserializer4JSONObject extends JsonDeserializer<JSONObject> {
        public JSONObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            TreeNode treeNode = p.getCodec().readTree(p);
            return JSON.parseObject(treeNode.toString());
        }
    }
}
